#include "stdafx.h"
#ifdef LIT_OGRE_SUPPORT
#include <LitOgreManager.h>
#include <LitApplicationSettings.h>
#include <LitRoot.h>

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
#include <X11Info.h>
#endif

LIT_API String Lit::OgreManager::Direct3D9RenderSystem("Direct3D9 Rendering Subsystem"); 
LIT_API String Lit::OgreManager::Direct3D11RenderSystem("Direct3D11 Rendering Subsystem"); 
LIT_API String Lit::OgreManager::OpenGLRenderSystem("OpenGL Rendering Subsystem");
LIT_API String Lit::OgreManager::OpenGL3RenderSystem("OpenGL 3+ Rendering Subsystem (EXPERIMENTAL)");
		
namespace Lit
{
	const OgrePlatform::Traits OgrePlatform::mPlatform = OGRE_PLATFORM;
	const OgrePlatform::Traits OgrePlatform::mArchitecture = OGRE_ARCH_TYPE;

	OgrePlatform::OgrePlatform( ){}
	OgrePlatform::~OgrePlatform(){}
		

	OgrePlatform::Traits OgrePlatform::type(){ return mPlatform; }
	OgrePlatform::Traits OgrePlatform::architecture(){ return mArchitecture; }
		
	bool OgrePlatform::isLinux(){ return mPlatform == OGRE_PLATFORM_LINUX; }
	bool OgrePlatform::isAndroid(){ return mPlatform == OGRE_PLATFORM_ANDROID; }
	bool OgrePlatform::isApple(){ return mPlatform == OGRE_PLATFORM_APPLE; }
	bool OgrePlatform::isAppleIOS(){ return mPlatform == OGRE_PLATFORM_APPLE_IOS; }
	bool OgrePlatform::isNaCL(){ return mPlatform == OGRE_PLATFORM_NACL; }
	bool OgrePlatform::isWin32(){ return mPlatform == OGRE_PLATFORM_WIN32; }

	bool OgrePlatform::is32Bits(){return mArchitecture == OGRE_ARCHITECTURE_32;}
	bool OgrePlatform::is64Bits(){return mArchitecture == OGRE_ARCHITECTURE_64;}

	OgrePlatform::WindowCreationParams OgrePlatform::getWindowParameters( const OgrePlatform::Window& win )
	{
		OgrePlatform::WindowCreationParams ret;
		Ogre::String handle(getWindowHandle(win));
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
			ret["externalWindowHandle"] = handle;
#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE
			ret["externalWindowHandle"] = handle;
			ret["macAPI"] = "cocoa";
			ret["macAPICocoaUseNSView"] = "true";
#elif OGRE_PLATFORM == OGRE_PLATFORM_LINUX
			ret["parentWindowHandle"] = handle;
#else
			// Implement this, sorry dude ...
#endif
		return ret;
	}
	OgrePlatform::WindowCreationParams OgrePlatform::getWindowParameters( const Ogre::String& ogre_handle )
	{
		OgrePlatform::WindowCreationParams ret;
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
			ret["externalWindowHandle"] = ogre_handle;
#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE
			ret["externalWindowHandle"] = ogre_handle;
			ret["macAPI"] = "cocoa";
			ret["macAPICocoaUseNSView"] = "true";
#elif OGRE_PLATFORM == OGRE_PLATFORM_LINUX
			ret["parentWindowHandle"] = ogre_handle;
#else
			// Implement this, sorry dude ...
#endif
		return ret;
	}
	Ogre::String OgrePlatform::getWindowHandle( const Window& win )
	{
		// Taken from QtOgre Framework
		Ogre::String mHandle;
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
		//positive integer for W32 (HWND handle) - According to Ogre Docs
		mHandle = Ogre::StringConverter::toString((unsigned int)(win.Handle));
#elif OGRE_PLATFORM == OGRE_PLATFORM_LINUX
		//poslong:posint:poslong:poslong (display*:screen:windowHandle:XVisualInfo*) for GLX - According to Ogre Docs
		QX11Info info;
		mHandle  = Ogre::StringConverter::toString((unsigned long)(info.display()));
		mHandle += ":";
		mHandle += Ogre::StringConverter::toString((unsigned int)(info.screen()));
		mHandle += ":";
		mHandle += Ogre::StringConverter::toString((unsigned long)(win.Handle));
		//mHandle += ":";
		//mHandle += Ogre::StringConverter::toString((unsigned long)(info.visual()));
#else
		// Implement this, sorry dude ...
#endif
		return mHandle;
	}
};

namespace Lit
{
	/*OpenGL 3+ Rendering Subsystem (EXPERIMENTAL)*/
	OgreManager::OgreManager(void)
		: Initialiser("OgreManager")
	{
	}


	OgreManager::~OgreManager(void)
	{
	}		
	
	OgreDetails& OgreManager::details(){ return getInitialState()->details(); }
	const OgreDetails& OgreManager::details() const{ return getInitialState()->details(); }

	void OgreManager::pushState( StatePtr s )
	{
		if( !s ) throw NullPointer("Invalid state", EXC_LOC);
		mStates.push( s );
		if( !s->isInitialised() )
		{
			if( mInitialState )
				s->details().assign( mWindow, mInitialState->details().sceneManager(), mInitialState->details().camera() );
			else s->details().setWindow( mWindow );
			s->createScene();
			s->setInitialised( true );
		}
	}
		
	void OgreManager::popState( )
	{
		if( mStates.empty() )
			throw RuntimeError( EXC_ITEM_MISSING, "State stack is empty", EXC_LOC );
		StatePtr s = mStates.top();
		if( !s ) throw NullPointer("Invalid state", EXC_LOC);
		mStates.pop();
		if( s->isInitialised() )			
		{
			s->destroyScene();
			s->setInitialised( false );
		}
		s.destroy();
	}
	
	void OgreManager::initialiseOgreBase( const String &xml_settings )
	{
		if( isInitialised() || Ogre::Root::getSingletonPtr() ) return;
		
		ApplicationSettings& settings(Lit::Root::Instance().getSettings()); 
		settings.load( xml_settings );
		Ogre::Root * mRoot = OGRE_NEW Ogre::Root(Ogre::StringUtil::BLANK,Ogre::StringUtil::BLANK, settings.getOgreProperty<String>("LogName", "LitFramework.log"));
		LIT_LOG( "OGRE3D: Initialising ..." );

		loadPlugins( settings.getOgreProperty<String>( "Plugins", "" ) );
		createRenderSystem( );
	}

	void OgreManager::initialise( const String &xml_settings, const String &windowTitle, const String& camera_name )
	{
		if( isInitialised() ) return;
		
		// If already initialised
		if( Ogre::Root::getSingletonPtr() )
			throw InitialisationError("Cannot create OGRE, already created", EXC_LOC);

		initialiseOgreBase(xml_settings);
		
		mWindow = Ogre::Root::getSingletonPtr()->initialise(true, windowTitle );	
		mInitialState = new OgreInitialState( camera_name, mWindow );
		pushState( *mInitialState );		

		setInitialised( true );
	}

	void OgreManager::initialise( const String &xml_settings, const OgrePlatform::Window & win, const String& camera_name )
	{
		if( isInitialised() ) return;
		
		// If already initialised
		if( Ogre::Root::getSingletonPtr() )
			throw InitialisationError("Cannot create OGRE, already created", EXC_LOC);

		initialiseOgreBase(xml_settings);
		
		Ogre::Root::getSingletonPtr()->initialise(false);	
		OgrePlatform::WindowCreationParams params(OgrePlatform::getWindowParameters( win ));
		mWindow = Ogre::Root::getSingletonPtr()->createRenderWindow(win.Title.empty()?String("OgreWindow"):win.Title, win.Width, win.Height, false, &params);
		
		mInitialState = new OgreInitialState( camera_name, mWindow );
		pushState( *mInitialState );		

		setInitialised( true );
	}
	void OgreManager::shutdown()
	{
		if( !isInitialised() ) return;
		LIT_LOG( "OGRE3D: Shutting down ..." );
		clearStates();
		Ogre::Root * root = Ogre::Root::getSingletonPtr();
		if( root )
		{
			OGRE_DELETE root;
			root = NULL;
		}
		
		setInitialised( false );
	}
	bool OgreManager::startRendering()
	{
		if(!isInitialised())
			throw InitialisationError("Cannot start rendering, Ogre hasn't been initialised !", EXC_LOC);

		while(true)
		{
			// Pump window messages for nice behaviour
			Ogre::WindowEventUtilities::messagePump();
 
			if(mWindow->isClosed())
				return false;
 
			// Render a frame
			try{
				if(!Ogre::Root::getSingletonPtr()->renderOneFrame()) return false;
				if( !_renderOneFrame( (1.0f/mWindow->getLastFPS()) ) ) return false;
			}
			catch( Lit::Exception e )
			{
				throw RuntimeError( EXC_RENDER_ERROR, "Cannot render frame: "+String(e.full()), EXC_LOC );
			}
			catch( Ogre::Exception e )
			{
				throw RuntimeError( EXC_RENDER_ERROR, "Cannot render frame: "+String(e.getFullDescription()), EXC_LOC );
			}
			catch( std::exception e )
			{
				throw RuntimeError( EXC_RENDER_ERROR, "Cannot render frame: "+String(e.what()), EXC_LOC );
			}
			
		}
	}
	bool OgreManager::_renderOneFrame( float dt )
	{
		if( !Lit::Root::Instance().renderOneFrame( dt ) ) return false;
		if( !getCurrentState()->renderOneFrame( dt ) ) return false;
		return true;
	}
	bool OgreManager::hasMoreStates() const { return mStates.empty(); }
	OgreManager::StatePtr& OgreManager::getCurrentState(){ 
		if( mStates.empty() ) throw RuntimeError("Cannot return current OGRE state, stack is empty", EXC_LOC);
		return mStates.top(); 
	}
	const OgreManager::StatePtr& OgreManager::getCurrentState() const{ 
		if( mStates.empty() ) throw RuntimeError("Cannot return current OGRE state, stack is empty", EXC_LOC);
		return mStates.top(); 
	}

	size_t OgreManager::getNumStates() const{ return mStates.size(); }
	
	String OgreManager::getLogName() const{ 
	#ifndef NDEBUG
		return Lit::Root::Instance().getSettings().getOgreProperty<String>("LogName", "LitFramework_d.log"); 
	#else
		return Lit::Root::Instance().getSettings().getOgreProperty<String>("LogName", "LitFramework.log"); 
	#endif
	}
	void OgreManager::loadPlugins( const String &plugins )
	{
		String plugs(plugins);
		Stringstream ss(plugs);
		while( !ss.eof() )
		{
			String plug;
			ss >> plug;
			Ogre::LogManager::getSingleton().logMessage("OGRE3D: Loading plugin "+plug);
			Ogre::Root * mRoot = Ogre::Root::getSingletonPtr();
			try{
				mRoot->loadPlugin( plug );
			}
			catch( Ogre::Exception e )
			{
				Ogre::LogManager::getSingleton().logMessage("OGRE3D: \t- failed: "+e.getDescription());
				throw InitialisationError("Cannot load OGRE plugin "+plug+", because: "+e.getDescription(), EXC_LOC);
			}
			catch( Lit::Exception e )
			{
				Ogre::LogManager::getSingleton().logMessage("OGRE3D: \t- failed: "+e.error());
				throw InitialisationError("Cannot load OGRE plugin "+plug+", because: "+e.error(), EXC_LOC);
			}
			catch( std::exception e )
			{
				Ogre::LogManager::getSingleton().logMessage("OGRE3D: \t- failed: "+String(e.what()));
				throw InitialisationError("Cannot load OGRE plugin "+plug+", because: "+e.what(), EXC_LOC);
			}
		}
	}
	Ogre::RenderSystem * OgreManager::createRenderSystem()
	{
		Ogre::LogManager::getSingleton().logMessage("OGRE3D: Creating RenderSystem");
		ApplicationSettings& settings(Lit::Root::Instance().getSettings()); 
		Ogre::RenderSystem * rs = Ogre::Root::getSingletonPtr()->getRenderSystemByName(settings.getRenderSystemName(OgreManager::Direct3D9RenderSystem));
		if( !rs ) throw InitialisationError("Invalid RenderSystem "+settings.getRenderSystemName(OgreManager::Direct3D9RenderSystem),EXC_LOC);
		
		ApplicationSettings::RenderSystemConfigMap map( Lit::Root::Instance().generateRenderSystemConfig(ASCL_MEDIUM) );
		for( auto i=map.begin(); i!=map.end(); i++ ) {
			rs->setConfigOption( i->first, i->second );
		}
		Ogre::Root::getSingletonPtr()->setRenderSystem( rs );
		return rs;
	}
	OgreCameraPtr OgreManager::getCamera() const{ return getCurrentState()->details().camera(); }
	OgreRenderWindowPtr OgreManager::getWindow() const{ return getCurrentState()->details().window(); }
	OgreSceneManagerPtr OgreManager::getSceneManager() const{ return getCurrentState()->details().sceneManager(); }

};


#endif